#pragma once
#include "ebmlio.hpp"
#include<list>

namespace MkvMux
{

class MkvStream;

class MkvContext
{
   MkvContext(const MkvContext&);
   MkvContext& operator=(const MkvContext&);    

public:

   EbmlIO::File m_file;

   MkvContext();
   ~MkvContext();
    
   void SetVideo(MkvStream*);
   void SetAudio(MkvStream*);

   void Open(IStream*);  
   void Close();
    
    //Once we have the requisite video frames, then we have to 
    //decide to mux the frames.
    //
    //We lead a cluster with a keyframe.  Ideallly, all audio
    //frames less or equal to the keyframe time go in the 
    //previous cluster.
    //
    //We receive notification that we have a new keyframe.
    //We have to wait for all audio frames that are less or
    //equal to the most recent keyframe (that one that triggers
    //the decision to flush to disk).
    //
    //If we have an end-of-stream indication, we don't wait
    //for any more audio frames for that pin.
    
    //We have a queue of video frames, that live somewhere.
    //We have a queue (or queues) of audio frames, that live
    //somewhere.
    //
    //Once we receive the trigger (video) frame, we enter a
    //ready state.  Then we monitor audio frames until we
    //receive the first audio frame that's greater than the 
    //the trigger frame.  Then we flush.
    
    //It's not clear whether we have to maintain a ptr 
    //to the streams here; OnReceive notification messages
    //could have a stream ptr as arg.  But then how to handle
    //the EOS stream.
    
   class Frame
   {
      Frame(const Frame&);
      Frame& operator=(const Frame&);
   protected:
      Frame();  //TODO: why do we need this?
      virtual ~Frame();
   public:
      virtual ULONG curr_timecode_ms() const = 0;
      virtual const BYTE* data() const = 0;
      virtual ULONG size() const = 0;
      virtual void Release();
   };
   
   class VideoFrame : public Frame
   {
      VideoFrame(const VideoFrame&);
      VideoFrame& operator=(const VideoFrame&);
   protected:
      VideoFrame();
   public:
      enum Type { kTypeIFrame, kTypePFrame, kTypeBFrame };
      virtual Type type() const = 0;
      
      virtual SHORT prev_timecode_ms() const = 0;
      //relative to curr_timecode_ms
      //must be less than 0
      
      virtual SHORT next_timecode_ms() const = 0;
      //relative to curr_timecode_ms
      //must be greater than 0
      
   };
   
   class AudioFrame : public Frame
   {
      AudioFrame(const AudioFrame&);
      AudioFrame& operator=(const AudioFrame&);
   protected:
      AudioFrame();  //TODO: the compiler says I need this: why?
   };      
              
    void WriteVideo(MkvStream*, VideoFrame*);  
    int EOSVideo(MkvStream*);
    bool WaitVideo() const;
      
    void WriteAudio(MkvStream*, AudioFrame*);
    int EOSAudio(MkvStream*);
    bool WaitAudio() const;
    
    ULONG GetTime() const;  //timecode[ms] of frame most recently written to file
    
private:
    
   MkvStream* m_pVideo;
   MkvStream* m_pAudio;  //TODO: accept multiple audio streams
   
   void Final();
   
   void WriteEbmlHeader();

   void InitSegment();
   void FinalSegment();    

   void InitFirstSeekHead();
   void FinalFirstSeekHead();
   void WriteSeekEntry(ULONG, __int64);

   void InitInfo();
   void FinalInfo();

   void WriteTrack();

   __int64 m_segment_pos;
   __int64 m_first_seekhead_pos;    
   __int64 m_second_seekhead_pos;
   __int64 m_info_pos;
   __int64 m_track_pos;
   __int64 m_cues_pos;
   __int64 m_duration_pos;    

   struct Cluster;
   struct Keyframe
   {
      ULONG m_timecode_ms;
      ULONG m_blockNumber;
     
      Keyframe();   //needed for std::list
      Keyframe(ULONG timecode_ms, ULONG blockNumber);
     
      void WriteCuePoint(MkvContext&, const Cluster&) const;
   };

   typedef std::list<Keyframe> keyframes_t;

   struct Cluster
   {
      __int64 m_pos;       //absolute pos within file
      ULONG m_timecode_ms;  //of this cluster
      keyframes_t m_keyframes;        
     
      void WriteCuePoints(MkvContext&) const;                
      //void Final(MkvContext&, ULONG&, const Cluster&);     
   };

   typedef std::list<Cluster> clusters_t;
 
   clusters_t m_clusters;
   ULONG m_max_timecode_ms;

   void WriteSecondSeekHead();
   void WriteCues();
   //void FinalClusters(__int64 pos);
   
#if 0
    bool ReadyToCreateNewClusterVideo(const VideoFrame*) const;
    void CreateNewClusterVideo(const VideoFrame*);    
#else
    bool ReadyToCreateNewClusterVideo(const VideoFrame&) const;
    void CreateNewCluster(const VideoFrame*);
#endif    

    void CreateNewClusterAudioOnly();

    void WriteVideoFrame(Cluster&, ULONG&);
    void WriteAudioFrame(Cluster&, ULONG&);   

    typedef std::list<VideoFrame*> vframes_t;
    typedef std::list<AudioFrame*> aframes_t;

    vframes_t m_vframes;
    aframes_t m_aframes;

    //This is a running count of the frames in the
    //most recent cluster created.  It's needed 
    //to mark the block number, which is used later
    //when creating the cues for the cluster.   
    //ULONG m_cFrames;   
    //TODO: we might be able to move this into
    //CreateNewClusterXXX method, and then pass it
    //as an arg to the WriteXXX op.

    //typedef vframes_t::iterator vframes_iter_t;
    //typedef std::list<vframes_iter_t> rframes_t;   
    typedef std::list<VideoFrame*> rframes_t;
    rframes_t m_rframes;

    bool m_bEOSVideo;
    bool m_bEOSAudio;
    int m_cEOS;
    int EOS(MkvStream*);

};


}  //end namespace MkvMux
